package com.yjf.ljr.util;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;

import com.yjf.ljr.common.Logger;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.AndroidHttpClient;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;

public class MyImageDownload {
	
	private static final int IO_BUFFER_SIZE = 4 * 1024;
	private static final int DEFAULT_BUFFER_SIZE = 32768;
	
	/** {@value */
	public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
	/** {@value */
	public static final int DEFAULT_COMPRESS_QUALITY = 100;
	protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
	protected int compressQuality = DEFAULT_COMPRESS_QUALITY;
	
    private static final String TAG = "MyImageDownload";
    
    private boolean isNeedLocalSave = false;
    private String saveFolderPath = "";
    
    private static final String HASH_ALGORITHM = "MD5";
	private static final int RADIX = 10 + 26; // 10 digits + 26 letters
	protected int bufferSize = DEFAULT_BUFFER_SIZE;

    private static final int HARD_CACHE_CAPACITY = 40;
    private static final int DELAY_BEFORE_PURGE = 30 * 1000; // in milliseconds

    // Hard cache, with a fixed maximum capacity and a life duration
    private final static HashMap<String, Bitmap> sHardBitmapCache =
        new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
        private static final long serialVersionUID = -7190622541619388252L;
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Bitmap> eldest) {
            if (size() > HARD_CACHE_CAPACITY) {
                // Entries push-out of hard reference cache are transferred to soft reference cache
                sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
                return true;
            } else {
                return false;
            }
        }
    };

    // Soft cache for bitmap kicked out of hard cache
    private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
        new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);

    private final Handler purgeHandler = new Handler();

    private final Runnable purger = new Runnable() {
        public void run() {
            clearCache();
        }
    };
    
    
    private static final ThreadPool threadPool = ThreadPool.getThreadPool();       
    
    public MyImageDownload(boolean isNeedLocalSave,String saveFolderPath) {
    	this.isNeedLocalSave = isNeedLocalSave;
    	this.saveFolderPath = saveFolderPath;
    }

    /**
     * Download the specified image from the Internet and binds it to the provided ImageView. The
     * binding is immediate if the image is found in the cache and will be done asynchronously
     * otherwise. A null bitmap will be associated to the ImageView if an error occurs.
     *
     * @param url The URL of the image to download.
     * @param imageView The ImageView to bind the downloaded image to.
     */  
    public void download(String url, ImageView imageView) {
    	if(TextUtils.isEmpty(url)) {
    		return;
    	}
    	download(url,imageView,null);
    }

    /**
     * Same as {@link #download(String, ImageView)}, with the possibility to provide an additional
     * cookie that will be used when the image will be retrieved.
     *
     * @param url The URL of the image to download.
     * @param imageView The ImageView to bind the downloaded image to.
     * @param cookie A cookie String that will be used by the http connection.
     */  
    public void download(String url, ImageView imageView, String cookie) {
    	if(TextUtils.isEmpty(url)) {
    		return;
    	}
    	 resetPurgeTimer();
         Bitmap bitmap = getBitmapFromCache(url);

         if (bitmap == null) {
             forceDownload(url, imageView, cookie);
         } else {
        	 cancelPotentialLoad(url,imageView);
             imageView.setImageBitmap(bitmap);
         }
    }

    /**
     * Same as download but the image is always downloaded and the cache is not used.
     * Kept private at the moment as its interest is not clear.
     */
    private void forceDownload(String url, ImageView imageView, String cookie) {
    	 if (url == null) {
             imageView.setImageDrawable(null);
             return;
         }
    	   	
    	if (cancelPotentialLoad(url, imageView)) {
    		Logger.d(TAG,"load");
			LoadTask task = new LoadTask(url,imageView,cookie);
			LoadedDrawable loadDrawable = new LoadedDrawable(task);
			imageView.setImageDrawable(loadDrawable);
			threadPool.execute(task);
		}
    }
    
    

    /**
     * Clears the image cache used internally to improve performance. Note that for memory
     * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
     */
    public void clearCache() {
        sHardBitmapCache.clear();
        sSoftBitmapCache.clear();
    }

    private void resetPurgeTimer() {
        purgeHandler.removeCallbacks(purger);
        purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
    }

    /**
     * Returns true if the current download has been canceled or if there was no download in
     * progress on this image view.
     * Returns false if the download in progress deals with the same url. The download is not
     * stopped in that case.
     */

    /**
     * @param imageView Any imageView
     * @return Retrieve the currently active download task (if any) associated with this imageView.
     * null if there is no such task.
     */

    /**
     * @param url The URL of the image that will be retrieved from the cache.
     * @return The cached bitmap or null if it was not found.
     */
    private Bitmap getBitmapFromCache(String url) {
        // First try the hard reference cache
        synchronized (sHardBitmapCache) {
            final Bitmap bitmap = sHardBitmapCache.get(url);
            if (bitmap != null) {
                // Bitmap found in hard cache
                // Move element to first position, so that it is removed last
                sHardBitmapCache.remove(url);
                sHardBitmapCache.put(url, bitmap);
                return bitmap;
            }
        }

        // Then try the soft reference cache
        SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
        if (bitmapReference != null) {
            final Bitmap bitmap = bitmapReference.get();
            if (bitmap != null) {
                // Bitmap found in soft cache
                return bitmap;
            } else {
                // Soft reference has been Garbage Collected
                sSoftBitmapCache.remove(url);
            }
        }

        return null;
    }
    
    public boolean removeBitmapFromCache(String url) {
    	if(TextUtils.isEmpty(url)) {
    		return false;
    	}
    	boolean result = false;
    	// First try the hard reference cache
        synchronized (sHardBitmapCache) {
        	
        	if(sHardBitmapCache.containsKey(url)) {
        		final Bitmap bitmap = sHardBitmapCache.get(url);
                if (bitmap != null) {
                	Logger.d(TAG, "removeBitmapFromCache, sHard");
                    sHardBitmapCache.remove(url);
                    bitmap.recycle();
                    result = true;
                }
        	}
        }

        // Then try the soft reference cache
        SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
        if (bitmapReference != null) {
            final Bitmap bitmap = bitmapReference.get();
            if (bitmap != null) {
            	Logger.d(TAG, "removeBitmapFromCache, sSoft");
            	sSoftBitmapCache.remove(url);
            	bitmap.recycle();
                result = true;
            } else {
                // Soft reference has been Garbage Collected
                sSoftBitmapCache.remove(url);
                result = true;
            }
        }

        return result;
    }

    /**
     * The actual task that will asynchronously download the image.
     */

    class LoadTask implements Runnable {

		private String url = null;
		private WeakReference<ImageView> imageViewReference;
		private boolean isRunning = true; 
		private LoadHandler mHandler = null;
		private String cookie = null;
		
		public LoadTask(String url,ImageView imageview,String cookie) {
			this.url = url;
			imageViewReference = new WeakReference<ImageView>(imageview);
			mHandler = new LoadHandler(imageViewReference,this);
			this.cookie = cookie;
		}
		
		public boolean isRunning(){
			return isRunning;
		}
		
		public void cancel() {
			isRunning = false;
		}
		public void recover() {
			isRunning = true;
		}
		
		@Override
		public void run() {
			if (isRunning) {
				Bitmap bitmap = getBitmapFromUrl(url,cookie);
				// Add bitmap to cache
				if (bitmap != null) {
					synchronized (sHardBitmapCache) {
						sHardBitmapCache.put(url, bitmap);
					}
				}
				Message msg = mHandler.obtainMessage();
				msg.obj = bitmap;
				mHandler.sendMessage(msg);
			}
		}		
	}
    
    /**
     * A fake Drawable that will be attached to the imageView while the download is in progress.
     *
     * <p>Contains a reference to the actual download task, so that a download task can be stopped
     * if a new binding is required, and makes sure that only the last started download process can
     * bind its result, independently of the download finish order.</p>
     */
    
    class LoadedDrawable extends ColorDrawable{
		private WeakReference<LoadTask> loadImageTaskReference;
		public LoadedDrawable(LoadTask loadTask) {
			super(Color.TRANSPARENT);
			loadImageTaskReference = new WeakReference<LoadTask>(loadTask);			
		}
		public LoadTask getLoadTask() {
			return loadImageTaskReference.get();
		}
	}
	
	
	
	class LoadHandler extends Handler{
		private WeakReference<ImageView> imageView = null;
		private LoadTask mLoadTask = null;
		public LoadHandler(WeakReference<ImageView> imageView,LoadTask loadTask){
			this.imageView = imageView;
			mLoadTask = loadTask;
		}
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if (Looper.myLooper() == Looper.getMainLooper()) {
				Bitmap bitmap = (Bitmap) msg.obj;
				if (imageView != null) {
					ImageView imageViewReal = imageView.get();
					LoadTask bitmapDownloaderTask = getLoadTask(imageViewReal);
					// Change bitmap only if this process is still associated
					// with it
					if (mLoadTask == bitmapDownloaderTask) {
						imageViewReal.setImageBitmap(bitmap);
					}
				}
			}
		}		
	}
	
	public LoadTask getLoadTask(ImageView imageView) {
		if(null != imageView) {
			Drawable drawable = imageView.getDrawable();
			if(drawable instanceof LoadedDrawable) {
				LoadedDrawable loadDrawable = (LoadedDrawable)drawable;
				return loadDrawable.getLoadTask();
			}
		}
		return null;
	}
	
	private Bitmap getBitmapFromUrl(String url,String cookie) {
		Bitmap bitmap = getBitmapFromFileUrl(url);		
		if(null == bitmap) {
			bitmap = getBitmapFromWebUrl(url,cookie);
		}		
		return bitmap;
	}
	
	private Bitmap getBitmapFromWebUrl(String url,String cookie) {
		 final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
         final HttpGet getRequest = new HttpGet(url);      
         if (cookie != null) {
             getRequest.setHeader("cookie", cookie);
         }

         try {
             HttpResponse response = client.execute(getRequest);
             final int statusCode = response.getStatusLine().getStatusCode();
             if (statusCode != HttpStatus.SC_OK) {
                 Log.w("ImageDownloader", "Error " + statusCode +
                         " while retrieving bitmap from " + url);
                 return null;
             }

             final HttpEntity entity = response.getEntity();
             if (entity != null) {
                 InputStream inputStream = null;
                 OutputStream outputStream = null;    
                 OutputStream os = null;
                 try {
                     inputStream = entity.getContent();
                     final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
                     outputStream = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
                     copy(inputStream, outputStream);
                     outputStream.flush();

                     final byte[] data = dataStream.toByteArray();
                     final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

                     // FIXME : Should use BitmapFactory.decodeStream(inputStream) instead.
                     //final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                     
                     if(isNeedLocalSave) {
                    	 String fileName = generateFileNameFromUrl(url);
                         os = new BufferedOutputStream(new FileOutputStream(fileName), bufferSize);
                         bitmap.compress(compressFormat, compressQuality, os);
                     }
                     
                     return bitmap;

                 } finally {
                     if (inputStream != null) {
                         inputStream.close();
                     }
                     if (outputStream != null) {
                         outputStream.close();
                     }
                     if (os != null) {
                    	 os.close();
                     }
                     entity.consumeContent();
                 }
             }
         } catch (IOException e) {
             getRequest.abort();
             Logger.w(TAG, "I/O error while retrieving bitmap from " + url);
         } catch (IllegalStateException e) {
             getRequest.abort();
             Logger.w(TAG, "Incorrect URL: " + url);
         } catch (Exception e) {
             getRequest.abort();
             Logger.w(TAG, "Error while retrieving bitmap from " + url);
         } catch(OutOfMemoryError e) {
        	 e.printStackTrace();
         } finally {
             if (client != null) {
                 client.close();
             }
         }
         return null;
	}
	
	 private void copy(InputStream in, OutputStream out) throws IOException {
         byte[] b = new byte[IO_BUFFER_SIZE];
         int read;
         while ((read = in.read(b)) != -1) {
             out.write(b, 0, read);
         }
     }
	 
	private Bitmap getBitmapFromFileUrl(String url) {
		String fileName = isNeedLocalSave ? generateFileNameFromUrl(url):url;
		File file = new File(fileName/*url*/);
		Bitmap bitmap = null;
		if(file.exists()) {
			FileInputStream fs = null;
			try {
				BitmapFactory.Options optnsSizeOnly = new BitmapFactory.Options();
				optnsSizeOnly.inJustDecodeBounds = true;
				BitmapFactory.decodeFile(file.getAbsolutePath(), optnsSizeOnly);
				BitmapFactory.Options opts = new BitmapFactory.Options();
				opts.inSampleSize = computeSampleSize(optnsSizeOnly, -1, 400*400/*800 * 800*/);
				opts.inDither = false; 
				opts.inInputShareable = true;
				opts.inTempStorage = new byte[16 * 1024];
				
				fs = new FileInputStream(file);
				bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(), null, opts);
			} catch (Exception e) {
				e.printStackTrace();
			} catch(OutOfMemoryError e) {
	        	 e.printStackTrace();
	        } finally {
	        	try {
					if (null != fs) {
						fs.close();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
	        }
		}
		return bitmap;
	}
    
	private int computeSampleSize(BitmapFactory.Options options,
			int minSideLength, int maxNumOfPixels) {
		int initialSize = computeInitialSampleSize(options, minSideLength,
				maxNumOfPixels);

		int roundedSize;
		if (initialSize <= 8) {
			roundedSize = 1;
			while (roundedSize < initialSize) {
				roundedSize <<= 1;
			}
		} else {
			roundedSize = (initialSize + 7) / 8 * 8;
		}

		return roundedSize;
	}

	private int computeInitialSampleSize(BitmapFactory.Options options,
			int minSideLength, int maxNumOfPixels) {
		double w = options.outWidth;
		double h = options.outHeight;

		int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
				.sqrt(w * h / maxNumOfPixels));
		int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
				Math.floor(w / minSideLength), Math.floor(h / minSideLength));

		if (upperBound < lowerBound) {
			// return the larger one when there is no overlapping zone.
			return lowerBound;
		}

		if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
			return 1;
		} else if (minSideLength == -1) {
			return lowerBound;
		} else {
			return upperBound;
		}
	}
	
	// 首先我们先通过cancelPotentialLoad方法去判断imageview是否有线程正在为它加载图片资源，
	// 如果有现在正在加载，那么判断加载的这个图片资源（url）是否和现在的图片资源一样，不一样则取消之前的线程（之前的下载线程作废）。
	// 见下面cancelPotentialLoad方法代码	 
	private boolean cancelPotentialLoad(String url,ImageView imageView) {
		LoadTask loadTask = getLoadTask(imageView);
		if(null != loadTask) {
			String bitmapUrl = loadTask.url;
			if((null == bitmapUrl) || (!bitmapUrl.equals(url))) {
				loadTask.cancel();
			} else {
				return false;
			}
		}
		return true;
	}
	
	private String generateFileNameFromUrl(String imageUri) {
		byte[] md5 = getMD5(imageUri.getBytes());
		BigInteger bi = new BigInteger(md5).abs();
		return saveFolderPath+bi.toString(RADIX);
	}
	
	private byte[] getMD5(byte[] data) {
		byte[] hash = null;
		try {
			MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
			digest.update(data);
			hash = digest.digest();
		} catch (NoSuchAlgorithmException e) {
		   e.printStackTrace();
		}
		return hash;
	}
}
